<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <style>
        @font-face {
            font-family: XiaolaiMonoSC-Regular;
            src: url('XiaolaiMonoSC-Regular.ttf');
        }

        body {
            text-align: center;
        }

        * {
            font-family: XiaolaiMonoSC-Regular;
        }

        .chooseChart {
            margin: 20px;
            border: 1px solid black;
            padding: 10px;
        }
    </style>
    <title>Adofly 0.1</title>
</head>

<body>
    <h1>Adofly</h1>
    <p>自制的冰与火之舞社区版游戏</p>
    <p style="color:red">全栈工程师已经写不动了，需要请超人，欢迎提交PR至仓库，我谢谢你全家</p>
    <div class="chooseChart">
        <p>1.不要乱传特效谱<br>2.读不了中旋和电梯还有别的乱七八糟的轨道判定有问题<br>3.音乐按钮纯摆设</p>
        <label for="chartInput">传铺面</label>
        <input type="file" id="chartInput">
        <br>
        <label for="musicInput">传音乐</label>
        <input type="file" id="musicInput">
        <br>
        <button id="loadFile">载入铺面</button>
    </div>
    <canvas id="gameCanvas" width="800" height="500" style="background-color: black;">
    </canvas><br>
    <input type="checkbox" id="autoplay">自动播放
    <p>项目地址：<a href='https://gitee.com/gr3yph4ntom/adofly'>https://gitee.com/gr3yph4ntom/adofly</a></p>
    <p>Made by 魇珩Gr3yPh4nton <a href='http://gr3yph.gt.tc'>http://gr3yph.gt.tc</a></p>
    <script>
        var angle = 0;
        var chart;
        var bpm = 80;
        var isReady = false;
        var isPlaying = false;
        var isFinished = false;
        var isFailed = false;
        var loadedFile = false;
        var angleData = [0, 90, 180, 90, 0, 90, 180, 90, 0, 90, 180, 90, 0, 90, 180, 90, 0, 90, 180, 90, 0, 90, 180, 90, 0, 90, 180, 90, 0];
        angleData.push(angleData.at(-1));
        var ballColor1 = 'red';
        var ballColor2 = 'blue';
        var counterclock = false;
        var x_start = 400 - 25;
        var y_start = 250;
        var current_rail = 0;
        var start_time;
        var deviation;
        var tooFast = 0;
        var acc = 100;
        var autoplay = false;
        var actions = [
            { "floor": 3, "eventType": "Twirl" },
            { "floor": 5, "eventType": "Twirl" },
            { "floor": 7, "eventType": "Twirl" },
            { "floor": 9, "eventType": "Twirl" },
            { "floor": 11, "eventType": "Twirl" },
            { "floor": 13, "eventType": "Twirl" },
            { "floor": 15, "eventType": "Twirl" },
            { "floor": 17, "eventType": "Twirl" },
            { "floor": 19, "eventType": "Twirl" },
            { "floor": 21, "eventType": "Twirl" },
            { "floor": 23, "eventType": "Twirl" },
            { "floor": 25, "eventType": "Twirl" },
            { "floor": 27, "eventType": "Twirl" }
        ];
        const canvas = document.getElementById('gameCanvas');
        const ctx = canvas.getContext('2d');

        // 处理高DPI显示器
        function setupCanvas(canvas) {
            // 获取设备像素比
            const dpr = window.devicePixelRatio || 1;
            // 获取canvas的CSS显示尺寸
            const rect = canvas.getBoundingClientRect();

            // 调整canvas的实际像素尺寸
            canvas.width = rect.width * dpr;
            canvas.height = rect.height * dpr;

            // 缩放绘图上下文
            ctx.scale(dpr, dpr);

            // 保持原有的绘图尺寸比例
            canvas.style.width = rect.width + 'px';
            canvas.style.height = rect.height + 'px';
        }

        // 初始化canvas
        setupCanvas(canvas);

        const reversion = new Image();
        reversion.src = 'image/counterclock.png';
        const rabbit = new Image();
        rabbit.src = 'image/rabbit.png';
        const snail = new Image();
        snail.src = 'image/snail.png';
        const beatAudio = new Audio('audio/beat.wav');
        function resetData() {
            if (!loadedFile) {
                angle = 180;
                bpm = 180;
                isReady = false;
                isPlaying = false;
                isFinished = false;
                isFailed = false;
                current_rail = 0;
                start_time = 0;
                deviation = 0;
                x_start = 400 - 25;
                y_start = 250;
                tooFast = 0;
                acc = 100;
                counterclock = false;
            }
            else {
                console.log('bpm:' + chart.settings.bpm);
                angle = 180;
                bpm = chart.settings.bpm;
                angleData = chart.angleData;
                actions = chart.actions;
                isReady = false;
                isPlaying = false;
                isFinished = false;
                isFailed = false;
                current_rail = 0;
                start_time = 0;
                deviation = 0;
                x_start = 400 - 25;
                y_start = 250;
                tooFast = 0;
                acc = 100;
                counterclock = false;
            }
        }
        function drawRails() {
            ctx.clearRect(0, 0, canvas.width, canvas.height);

            // 绘制黄色轨道
            ctx.beginPath();
            ctx.strokeStyle = '#fca607';
            ctx.lineWidth = 25;
            var x = x_start;
            var y = y_start;
            var currentAngle = 0;
            var currentRail = 0;
            angleData.forEach((railAngle,railIndex) => {
                ctx.moveTo(x, y);
                if(Math.abs(current_rail-railIndex)  <= 5){
                drawLineAngle(currentAngle, x, y, 25);
                }
                x = x + 25 * Math.cos(currentAngle / 180 * Math.PI);
                y = y - 25 * Math.sin(currentAngle / 180 * Math.PI);
                if(Math.abs(current_rail-railIndex)  <= 5){
                drawLineAngle(railAngle, x, y, 25);
                }
                x = x + 25 * Math.cos(railAngle / 180 * Math.PI);
                y = y - 25 * Math.sin(railAngle / 180 * Math.PI);
                currentAngle = railAngle;
            });
            ctx.stroke();

            // 重置位置用于后续绘制
            x = x_start;
            y = y_start;
            currentAngle = 0;
            angleData.forEach((railAngle,railIndex) => {
                x = x + 25 * Math.cos(currentAngle / 180 * Math.PI);
                y = y - 25 * Math.sin(currentAngle / 180 * Math.PI);
                if(Math.abs(current_rail-railIndex)<=5){
                actions.forEach(function (item) {
                    if (currentRail == item.floor) {
                        if (item.eventType == 'Twirl') {
                            ctx.drawImage(reversion, x - 13, y - 13, 25, 25);
                        } else if (item.eventType == 'SetSpeed') {
                            if (item.speedType == 'Multiplier') {
                                if (item.bpmMultiplier > 1) {
                                    ctx.drawImage(rabbit, x - 13, y - 13, 25, 25);
                                }
                                else {
                                    ctx.drawImage(snail, x - 13, y - 13, 25, 25);
                                }
                            } else {
                                if (item.beatsPerMinute > bpm) {
                                    ctx.drawImage(rabbit, x - 13, y - 13, 25, 25);
                                } else {
                                    ctx.drawImage(snail, x - 13, y - 13, 25, 25);
                                }
                            }
                        }
                    }
                    if (currentRail == angleData.length - 1) {
                        ctx.beginPath();
                        ctx.arc(x, y, 13, 0, Math.PI * 2);
                        ctx.fillStyle = 'white';
                        ctx.fill();
                    }
                })
            }
                x = x + 25 * Math.cos(railAngle / 180 * Math.PI);
                y = y - 25 * Math.sin(railAngle / 180 * Math.PI);
                currentAngle = railAngle;
                currentRail++;
            });
        }

        function drawLineAngle(angle, x_begin, y_begin, len) {
            angle = angle / 180 * Math.PI;
            x_end = x_begin + len * Math.cos(angle);
            y_end = y_begin - len * Math.sin(angle);
            ctx.lineTo(x_end, y_end);
        }

        function drawBalls() {
            ctx.beginPath();
            ctx.arc(400, 250, 13, 0, Math.PI * 2);
            ctx.fillStyle = ballColor1;
            ctx.fill();

            ctx.beginPath();

            ctx.arc(400 - 50 * Math.cos(angle / 180 * Math.PI), 250 - 50 * Math.sin(angle / 180 * Math.PI), 13, 0, Math.PI * 2);
            //ctx.arc(400 - 50 * Math.cos(angle / 180 * Math.PI), 250 + 50 * Math.sin(angle / 180 * Math.PI), 13, 0, Math.PI * 2);

            ctx.fillStyle = ballColor2;
            ctx.fill();
        }

        function ballLoop() {
            if (isReady) {
                // 确保每次旋转5度，根据BPM计算间隔时间
                // 对于每拍转半圈(180度)的要求，需要：
                // - 每拍需要180/5=36次循环
                // - 每拍时间为60000/BPM毫秒
                // - 所以每次循环间隔为 (60000/BPM)/36 = 10000/(6*BPM) 毫秒

                if (!counterclock) {
                    angle += 5;
                } else {
                    angle -= 5;
                }
                angle = simplifyAngleToRange(angle);
                deviation = Math.abs(Math.abs(angle + angleData[current_rail]) - 180);
                drawRails();
                drawBalls();
                if (deviation == 0 && isPlaying) {
                    if (autoplay) {
                        switchBall();
                    }
                }
                //console.log(angle,simplifyAngleToRange(180-angleData[current_rail]+60));
                if (!counterclock) {
                    if (angle == simplifyAngleToRange(180 - (angleData[current_rail] - 60)) && isPlaying) {
                        isFailed = true;
                        isFinished = true;
                        isPlaying = false;
                    }
                }
                else {
                    if (angle == simplifyAngleToRange(180 - angleData[current_rail] - 60) && isPlaying) {
                        isFailed = true;
                        isFinished = true;
                        isPlaying = false;
                    }
                }
                if (tooFast >= 3) {
                    isFailed = true;
                    isFinished = true;
                    isPlaying = false;
                }
                if (!isFinished && !isPlaying) {
                    if (Date.now() - start_time <= 1000 * (60 / bpm)) {
                        ctx.font = '50px XiaolaiMonoSC-Regular';
                        ctx.fillStyle = 'white';
                        ctx.textAlign = 'center';
                        ctx.baseLine = 'middle';
                        ctx.fillText('3', 400, 250);
                    } else if (Date.now() - start_time <= 2000 * (60 / bpm)) {
                        ctx.font = '50px XiaolaiMonoSC-Regular';
                        ctx.fillStyle = 'white';
                        ctx.textAlign = 'center';
                        ctx.baseLine = 'middle';
                        ctx.fillText('2', 400, 250);
                    } else if (Date.now() - start_time <= 3000 * (60 / bpm)) {
                        ctx.font = '50px XiaolaiMonoSC-Regular';
                        ctx.fillStyle = 'white';
                        ctx.textAlign = 'center';
                        ctx.baseLine = 'middle';
                        ctx.fillText('1', 400, 250);
                    } else if (Date.now() - start_time <= 4000 * (60 / bpm)) {
                        ctx.font = '50px XiaolaiMonoSC-Regular';
                        ctx.fillStyle = 'white';
                        ctx.textAlign = 'center';
                        ctx.baseLine = 'middle';
                        ctx.fillText('开始！', 400, 250);
                    } else {
                        isPlaying = true;
                    }
                } else if (isFinished && !isPlaying) {
                    if (!isFailed) {
                        if (acc != 100) {
                            ctx.font = '50px XiaolaiMonoSC-Regular';
                            ctx.fillStyle = 'white';
                            ctx.textAlign = 'center';
                            ctx.baseLine = 'middle';
                            ctx.fillText('恭喜！', 400, 250);
                        } else {
                            ctx.font = '50px XiaolaiMonoSC-Regular';
                            ctx.fillStyle = 'white';
                            ctx.textAlign = 'center';
                            ctx.baseLine = 'middle';
                            ctx.fillText('完美无瑕！', 400, 250);
                        }
                    } else {
                        ctx.font = '50px XiaolaiMonoSC-Regular';
                        ctx.fillStyle = 'white';
                        ctx.textAlign = 'center';
                        ctx.baseLine = 'middle';
                        if (tooFast >= 3) {
                            ctx.fillText('按太快！', 400, 250);
                        } else {
                            ctx.fillText('完成度' + parseInt(current_rail / angleData.length * 100) + '%', 400, 250);
                        }
                    }
                }
            } else {
                ctx.clearRect(0, 0, canvas.width, canvas.height);
                ctx.font = '50px XiaolaiMonoSC-Regular';
                ctx.fillStyle = 'white';
                ctx.textAlign = 'center';
                ctx.baseLine = 'middle';
                ctx.fillText('点击或按任意键开始', 400, 250);
            }
            ctx.font = '20px XiaolaiMonoSC-Regular';
            ctx.textAlign = 'left';
            ctx.baseLine = 'top';
            
            // 当自动播放开启时，显示亮绿色的"自动播放"提示
            if (autoplay) {
                ctx.fillStyle = '#00FF00'; // 亮绿色
                ctx.fillText('自动播放', 10, 20);
            }
            
            // 将BPM和精准度显示在右上角，确保颜色为白色
            ctx.textAlign = 'right';
            ctx.fillStyle = 'white';
            ctx.fillText('BPM: ' + bpm, 750, 20);
            ctx.fillText('精准度：' + acc.toFixed(2) + '%', 750, 50);
            //console.log(simplifyAngleToRange(angle));
            // 计算每拍转半圈所需的间隔
            // 每拍时间为 (60000 / bpm) 毫秒
            // 每拍需要旋转180度，每次循环旋转5度，所以需要36次循环
            // 因此每次循环间隔为 (60000 / bpm) / 36 = 10000 / (6 * bpm)
            setTimeout(ballLoop, 10000 / (6 * bpm));
        }
        document.addEventListener('keydown', function (event) {
            switchBall();
        });
        canvas.addEventListener('click', function (event) { switchBall(); })
        function switchBall() {
            if (isFinished) {
                resetData();
                return;
            }
            console.log('Current angle:', simplifyAngleToRange(angle), '; Target angle:', angleData[current_rail]);
            if (!isReady) { isReady = true; start_time = Date.now(); }
            if (!isPlaying) return;

            console.log('Deviation:', deviation);
            if (deviation > 60) {
                tooFast++;
                acc -= 100 / (angleData.length * 80);
            } else if (deviation <= 60 && deviation >= 45) {
                tooFast = 0;
                acc -= 100 / (angleData.length * 60);
            } else if (30 <= deviation && deviation <= 45) {
                tooFast = 0;
                acc -= 100 / (angleData.length * 25);
            } else if (0 <= deviation && deviation <= 30) {
                tooFast = 0;
            }
            if (deviation > 60) {
                return;
            }
            if (deviation > 0) {
                //beatAudio.play();
            }
            //if (Math.abs(Math.abs(simplifyAngleToRange(angle)) - (180 - angleData[current_rail])) > 20) return;
            if (current_rail > angleData.length - 2) return;
            if (ballColor1 == 'red') {
                ballColor1 = 'blue';
                ballColor2 = 'red';
            } else {
                ballColor1 = 'red';
                ballColor2 = 'blue';
            }
            x_start -= Math.cos(angleData[current_rail] / 180 * Math.PI) * 50;
            y_start += Math.sin(angleData[current_rail] / 180 * Math.PI) * 50;
            //if (!counterclock) {
            angle = -angleData[current_rail];
            //} else {
            //    angle = angleData[current_rail];
            //}
            current_rail++;
            actions.forEach(function (item) {
                if (current_rail == item.floor) {
                    if (item.eventType == 'Twirl') {
                        if (counterclock) {
                            counterclock = false;
                        } else {
                            counterclock = true;
                        }
                    } else if (item.eventType == 'SetSpeed') {
                        if (item.speedType == 'Multiplier') {
                            bpm = bpm * item.bpmMultiplier;
                        } else {
                            bpm = item.beatsPerMinute;
                        }
                    }
                }
            })
            if (current_rail == angleData.length - 1) {
                isFinished = true;
                isPlaying = false;
            }

        }
        document.getElementById('loadFile').addEventListener('click', function () {
            if (!chartInput.files[0]) {
                alert('您好歹传个谱吧');
                return;
            }
            //将传入的铺面作为json解析 解析失败就弹窗提示
            var reader = new FileReader();
            reader.readAsText(chartInput.files[0]);
            reader.onload = function () {
                try {
                    console.log('尝试解析JSON文件内容:', reader.result.substring(0, 200) + '...'); // 显示前200个字符用于调试

                    // 增强的预处理方法
                    let jsonText = reader.result;

                    // 1. 处理BOM字符（如果存在）
                    if (jsonText.charCodeAt(0) === 0xFEFF) {
                        jsonText = jsonText.substring(1);
                        console.log('已移除BOM字符');
                    }

                    try {
                        // 尝试直接解析
                        chart = JSON.parse(jsonText);
                        console.log('JSON直接解析成功！');
                    } catch (firstError) {
                        console.log('直接解析失败，尝试预处理...');

                        // 预处理步骤1: 将单引号属性名转换为双引号
                        jsonText = jsonText.replace(/'(\w+)':/g, '"$1":');

                        // 预处理步骤2: 将属性名周围的空格去除（如果有）
                        jsonText = jsonText.replace(/"\s+([\w]+)\s*":/g, '"$1":');

                        // 预处理步骤3: 处理多余的逗号（对象末尾或数组末尾）
                        jsonText = jsonText.replace(/,\s*(\}\s*|\]\s*)/g, '$1');

                        // 再次尝试解析
                        chart = JSON.parse(jsonText);
                        console.log('预处理后JSON解析成功！');
                    }

                    loadedFile = true;
                    resetData();
                } catch (error) {
                    console.error('JSON解析错误:', error);
                    alert('诶，铺面解析失败了...错误信息：' + error.message);
                    return;
                }
            }
            reader.onerror = function (event) {
                console.error('文件读取错误:', event);
                alert('文件读取失败，请检查文件是否可读');
            };
        })
        document.getElementById('autoplay').addEventListener('click', function () {
            autoplay = !autoplay;
            if (autoplay) {
                this.textContent = '关闭自动播放';
            } else {
                this.textContent = '开启自动播放';
            }
        })

        /**
         * 使用诱导公式将角度简化到-180°到180°之间
         * @param {number} angle - 角度值（角度制）
         * @returns {number} 简化后的角度值（-180°到180°之间）
         */
        function simplifyAngleToRange(angle) {
            // 先将角度除以360，获取余数
            angle = angle % 360;

            // 如果余数大于180°，则减去360°使其位于-180°到180°之间
            // 如果余数小于-180°，则加上360°使其位于-180°到180°之间
            if (angle > 180) {
                angle -= 360;
            } else if (angle < -180) {
                angle += 360;
            }

            return angle;
        }
        resetData();
        //setInterval(ballLoop, 10000 / (6 * bpm));
        ballLoop();

    </script>
</body>

</html>